<?php
/**
 * 这个类的主要功能是分析HTML模型，用选择器的方式获取HTML
 * 可以直接像JQUERY一样进行选择
 * @author LiuYong 
 * @email jedece@qq.com
 * @link http://www.caijiche.com完全免费的采集工具
 */

require_once 'inc/Log.php';

mb_internal_encoding("UTF-8");

define('SPACE',' ');//空格常量


define("TAG_START","TAG_START");//读取文本内容
define("TAG_NAME","TAG_NAME");
define("CLOSE_TAG_NAME","CLOSE_TAG_NAME");
define("ATTR_NAME","ATTR_NAME");//读取属性
define("ATTR_VAL","ATTR_VAL");//读取属性值
define("TAG_END","TAG_END");//标签的结束，遇到符号“>”
define("TAG_OVER","TAG_OVER");//标签的结束，遇到符号“>”,并且是结束标签的时候
define("COMMON_TEXT", "COMMON_TEXT");


define("NO_USE","NO_USE");//没用的字符
define("EQUAL_SYMBOL","EQUAL_SYMBOL");//等于号
define("VAL_LEFT","VAL_LEFT");//读取属性值左边引号
define("VAL_RIGHT","VAL_RIGHT");//读取属性值左边引号

define("READING_COMMON_TEXT", "READING_COMMON_TEXT");
define("READING_TAG", "READING_TAG");
define("READING_TAG_NAME","READING_TAG_NAME");//在标签里面
define("READING_TAG_ATTR_NAME", "READING_TAG_ATTR_NAME");
define("READING_CLOSE_TAG", "READING_CLOSE_TAG");//读取关闭标签

define("READING_TAG_ATTR_VAL_START", "READING_TAG_ATTR_VAL_START");//准备开始读取属性值
define("READING_TAG_ATTR_VAL", "READING_TAG_ATTR_VAL");//正在读取属性值.

define("ROOT_NODE","root");





class HtmlDom{
	
	public $nodes = array();//结点树，结点数组
	
	public $doc;
	
	public $size=0;//html文件的大小
	
	
	private $pos=0;//初始游标
	
	//private $cursor;//活动游标
	
	private $char;//当前字符
	
	private $charInfo;
	
	/**
	 * 0:没有在任何标签控制范围内 READ_HTML
	 * 1：进入一个标签的开始 READ_TAG
	 * 2：成功获取标签名后 
	 * 3：成功获取标签的属性
	 * 4：成功获取一个标签<div id="abc">开始获取标签内容
	 * 5：成功获取标签的内容
	 * 6：成功进入一个结束标签内容
	 * 7：成功获取结束标签名
	 * 8：获取结束标签完成
	 */
	private $status=READING_COMMON_TEXT;//分析所处的状态,依据当前字符的语义分析得到

	private $tmpNodes=array();//临时的结点，没有结束的结点标识
	private $nodeCount = 0;//结点计数器
	
	private $attrValSep = NULL;	//记录一个属性值的分隔符，是双引号还是单引号或什么都没有？
//	private $nodePos = NULL;//结点游标

	
	private $singleLineNode = array("!doctype","meta","link","br","input","img","br\\","wbr","iframe");//这里定义没有或不可能有子节点的元素，这些在处理的时候，不会做为任何元素的父节点。
	private $noChildNode = array("style","textarea","script");
	private $ignoreNode = array("style","script","br","wbr");//在解析DOM树时，忽略的结点
	
	//private $escapeFlag = False;//转义标识，在解析HTML的时候，遇到一个“\"”这样的字符，我们认为是转义成了一个引号的值

	/**
	 * 构造函数
	 * @param String $str
	 */
	function __construct($str=NULL){
		if(!is_null($str)){
			
			$this->doc = $this->removeNoise($str);
			//去除HTML中没用的信息
			$this->size = strlen($this->doc);// 文件的长度
			
			//log::debug($this->doc);
			
			if($this->size>0){
				$rootNode = new HtmlDomNode();
				$rootNode ->startPos = 0;
				$rootNode ->endPos = $this->size;
				$rootNode ->tag = ROOT_NODE;
				$this->tmpNodes["N_".$this->nodeCount++] = $rootNode;
				$this->initDom();
				//var_dump($this->nodes);
			}
		}
	}
	/**
	 * 使用节点的编号查找到这个节点的HTML信息
	 */
	public function getNodeHtml($nodeIndex){
		$node = $this->nodes[$nodeIndex];
		$startPos = $node->startPos;
		$endPos = $node->endPos;
		return substr($this->doc,$startPos,$endPos - $startPos + 1);//将结果保存到返回值当中去
	}
	
	/**
	 * 查询的主要函数
	 * @param unknown_type $selector
	 */
	public function find($selector){
		$html = array();//定义返回值
		//if($selector == NULL || strlen($selector)<=0)return NULL;//返回空数据
		$selectors = $this->parseSelector($selector);//将选择器解析成数组
		
		//var_dump($selectors);
		
		if (($count=count($selectors))===0) return array();
		
		//var_dump($selectors2);
		//$p = "N_0";//默认从根节点开始
		for($i=0;$i<count($selectors);$i++){
			//每一个用逗号分开的选择器，多个选择器可以合并在一起
			$p = $this->seek($selectors[$i]);//返回的是一个数组//这是查找一的结点列表
			
			//var_dump($p);
			//echo "+++++++++++++++++++++++++++++++++++++++++++++";
			for($j=0;$j<count($p);$j++){
				//echo "$i;$j";
				$node = $this->nodes[$p[$j]];
				$startPos = $node->startPos;
				$endPos = $node->endPos;
				$html[$i][$j]= substr($this->doc,$startPos,$endPos - $startPos + 1);//将结果保存到返回值当中去
			}
		}
		
		//$newDom = new HtmlDom(implode("",$html));//返回对自身的调用？

		return $html;//返回HTML信息

	}
	/**
	 * 查找一个元素，返回这个元素的数组下标
	 * @param Array $selector//这是一个二维数组
	 * @param String $startPos
	 * @param Int $maxDepth 查找的最大深度
	 */
	private function seek($selector,$cursor="N_0"){
		
		$result = array();//这是一个结果数组
		//if(--$maxDepth<0)return $result;
		
		$len = count($selector);//选择器的层次数
		if(!is_array($selector) || $len == 0)return $result;//如果选择器没有内容后
		$parentNode = $this->nodes[$cursor];
		$children = $parentNode->children;

		//选择器的过虑对象
		$tag = $selector[0][0];
		$key = $selector[0][1];
		$val = $selector[0][2];
		$exp = $selector[0][3];
		$cl = $selector[0][4];
		$clval = $selector[0][5];
		$child = $selector[0][6];//如果是大于号，则只查找本结点，如果是空格，则可以在所有子节点，孙子节点里查找
		$no_key = $selector[0][7];
		
			
		for($i=0;$i<count($children);$i++){
			$nodeId = $children[$i];
			$currentNode = $this->nodes[$nodeId];//遍历的当前节点
			
			$tagName = $currentNode->tag;
				

			//判断这个结点是否符合当前的选择器
			$match = TRUE;//默认是匹配

			if(isset($tag) && $tag!=="*"){//如果设置了标签
				if($tag != $tagName){
					$match=FALSE;
				}
			}else{
				return array();//如果没有发现这个TAG，则可能是选择器出错了
			}
			
				//var_dump($exp);
			if(isset($key)){
				if(!isset($currentNode->attr[$key])){
					//如果发现这个标签没有这个属性，则返回不匹配
					$match=FALSE;
				}else{
					//如果已经设置了属性的值，则要匹配这个属性的值
					$attVal = $currentNode->attr[$key];//属性的值
					if(isset($val)){
						//这里要对匹配的几种情况进行处理
						//对表达式的操作符进行处理
						switch ($exp){
							case "=":
								if($attVal!=$val)$match=FALSE;
								break;
							case "|="://属性包选择器 选择指定属性等于给定字符串或以该字符串为前缀（该字符串后跟一个连字符"-" ）的元素。
								if(strpos($attVal, $val."-")===false)$match=FALSE;//没有找到
								break;
							case "*="://选择指定属性具有包含一个给定的子字符串的元素。（选择给定的属性是以包含某些值的元素）
								if(strpos($attVal, $val)===false)$match=FALSE;
								break;
							case "~=":// 属性选择器选择指定属性用空格分隔的值中包含一个给定值的元素。
								$atts = explode(" ",$attVal);
								if(!is_array($atts) || !in_array($val, $atts))$match=FALSE;
								break;
							case "$="://属性选择器选择指定属性是以给定值结尾的元素。这个比较是区分大小写的。{暂时不支持大小写}
								if(strpos($attVal, $val)!==strlen($attVal)-strlen($val))$match=FALSE;
								break;
							case "!="://属性选择器选择不存在指定属性，或者指定的属性值不等于给定值的元素。
								if($attVal==$val)$match=FALSE;
								break;
							case "^="://属性选择器选择指定属性是以给定字符串开始的元素
								if(strpos($attVal, $val)!==0)$match=FALSE;
								break;
							default://不被支持的表达式
								$match=FALSE;
						}
					}
				}
			}
			
			//开始对部分伪类选择器进行处理
			switch ($cl){
				case "eq"://Selector选择一个给定索引值的元素。（从 0 开始计数）
					if($clval != $i)$match=FALSE;
					break;
					
				case "gt"://:gt() Selector选择所有大于给定索引值的元素
					if($i <= $clval)$match=FALSE;
					break;
					
				case "first":// Selector选择第一个匹配的元素。
					if(0 != $i)$match=FALSE;
					break;
					
				case "first-child":// Selector选择第一个匹配的元素。
					if(0 != $i)$match=FALSE;
					break;
					
				case "even"://:even Selector选择偶数元素，从 0 开始计数。同样查看奇数
					if($i%2 !== 0)$match=FALSE;
					break;
					
				case "odd"://选择奇数元素，从 0 开始计数。同样查看偶数 特别地注意的是，这是基于0的索引，所以:odd选择器是选择第二个元素，第四个元素，依此类推在匹配。
					if($i%2 == 0)$match=FALSE;
					break;
				
				case "button":// Selector选择所有按钮元素和类型为按钮的元素。
					if($tagName != "button" && ($tagName != "input" || ($tagName == "input" && $currentNode->attr["type"]!="button")))$match=FALSE;
					break;
				
				case "checkbox "://Selector选择所有类型为复选框的元素。
					if(!($tagName == "input" && isset($currentNode->attr["type"]) && $currentNode->attr["type"]=="checkbox"))$match=FALSE;

					break;
					
				case "checked":// Selector选择所有勾选的元素。
					if(!isset($currentNode->attr["checked"]))$match=FALSE;
					break;
					
				case "disabled"://Selector选择所有被禁用的元素。
					if(!isset($currentNode->attr["disabled"]))$match=FALSE;
					break;
					
				case "enabled"://Selector选择所有可用的元素。
					if(isset($currentNode->attr["disabled"]))$match=FALSE;
					break;
					
				case "empty"://Selector选择所有没有子元素的元素（包括文本节点）。
					if(count($currentNode->children)!=0)$match=FALSE;
					break;
					
				case "file"://:file Selector选择所有类型为文件的元素。
					if(!($tagName == "input" && isset($currentNode->attr["type"]) && $currentNode->attr["type"]=="file"))$match=FALSE;
					break;
					
				case "header"://Selector选择所有标题元素，像h1, h2, h3 等.
					if($tagName!="h1" & $tagName!="h2" & $tagName!="h3" & $tagName!="h4" & $tagName!="h5" & $tagName!="h6")$match=FALSE;
					break;
					
				case "image"://Selector选择所有图像类型的元素。"
					if($tagName!="image")$match=FALSE;
					break;
					
				case "input"://Selector选择所有 input, textarea, select 和 button 元素
					if($tagName!="input" & $tagName!="textarea" & $tagName!="select" & $tagName!="button")$match=FALSE;
					break;
			}
			
			if($match){
				//泛查找
				if($len-1>=1){//如果还没有解析完成这个结，则还要再去检测
					$newSelector = $selector;
					array_shift($newSelector);//将第一个选择器弹出，因为已经匹配完成
					$childResult = $this->seek($newSelector,$nodeId);//如果选择器没有结束，则继续向下寻找
					if(count($childResult)){//如果子结点里找到了
						$result= array_merge($result, $childResult);
					}
				}else{
					$result[] = $nodeId;//如果这是最后一个结点，则将最终找到的结果返回
				}

			}else{//如果当前结点不匹配的情况下
				if($child != ">"){//如果这是直接找子结点的情况，则不能去它的子结点继续查找
					$result_children = $this->seek($selector,$nodeId);//这里进行递归调用 ，在数组里进行查找
					$result = array_merge($result,$result_children);//合并数组,将子结点的所有结果合并到一起
				}
			}
		}

		return $result;//返回没有找到的信息
	}
	
	/**
	 * 将HTML的没用的信息清除
	 */
	private function removeNoise($str){
		$str = preg_replace("'<\!.+?>'is", "", $str);//去除注释等信息
		$str = preg_replace("'<#--.*?-->'is", "", $str);//去除注释等信息
		$str = preg_replace("'<script.+?</script>'is", "", $str);
		$str = preg_replace("'<style.+?</style>'is", "", $str);
		$str = preg_replace("'<br\\\\>'is", "<br/>", $str);
		return $str;
	}

	/**
	 * 分析选择器
	 * 将选择器转换成数组
	 */
	private function parseSelector($selector){

//		$pattern = "/([\w-:\*]*)  (?#匹配一个 标签名 冒号 *号) m11
//		             (?:\#([\w-]+)|\.([\w-]+))?  (?#这是一个不捕获的匹配 #abc 或 .abcd) m2 m3
//		             (?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?  (?#这是一个不捕获的匹配  [@!abc='abc']) m4 m5 m6 m5="!= *= ^= $="
//		             ([\/, ]+)/is";  (?#这是一个不捕获的匹配  /, ) m7

		$pattern = "/([ >]*)?([\w-\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$\|~]?=)[\"']?(.*?)[\"']?)?\])?(?:\:([\w-]+)(?:\((\d*)\))?)?([\/,]*)/is";

		preg_match_all($pattern, trim($selector).' ', $matches, PREG_SET_ORDER);
		
		//var_dump($matches);
		
		$selectors = array();
		$result = array();
		
		foreach ($matches as $m) {
			$m[0] = trim($m[0]);
			if ($m[0]==='' || $m[0]==='/' || $m[0]==='//') continue;
			// for browser generated xpath
			if ($m[1]==='tbody') continue;

			list($tag, $key, $val, $exp, $cl, $clval,$child, $no_key) = array($m[2], null, null, "=", null, null, null, false);
			if (!empty($m[1])) {$child=$m[1];}
			if (!empty($m[3])) {$key='id'; $val=$m[3];}//第三个元素：属性选择器
			if (!empty($m[4])) {$key='class'; $val=$m[4];}//第四个元素：属性值
			if (!empty($m[5])) {$key=trim($m[5]);}//第五元素：
			if (!empty($m[6])) {$exp=$m[6];}
			if (!empty($m[7])) {$val=$m[7];}
			if (!empty($m[8])) {$cl=$m[8];}
			if (!is_null($m[9])) {$clval=$m[9];}
			//if (!is_null($m[9])) {$child=$m[9];}
			//log::debug("clval:$clval");
			// convert to lowercase
			$tag=empty($tag)?"*":strtolower($tag); //第二个元素：标签名
			$key=is_null($key)?NULL:strtolower($key); 
			$cl=is_null($cl)?NULL:strtolower($cl); 
			$child=is_null($child)?NULL:trim($child); 
			//$key=strtolower($key);
			//elements that do NOT have the specified attribute
			if (isset($key[0]) && $key[0]==='!') {$key=substr($key, 1); $no_key=true;}

			$result[] = array($tag, $key, $val, $exp,$cl,$clval,$child, $no_key);
			
			//用逗号做分隔符的时候标记一下
			if (trim($m[10])===',') {
				$selectors[] = $result;//将这个结存起来
				$result = array();
			}
		}
		if (count($result)>0)
			$selectors[] = $result;
/**
 *           0 => string '*' (length=1)
          1 => null 属性
          2 => null 属性值
          3 => string '=' (length=1) 属性值与属性的关系
          4 => string 'checked' (length=7) 伪类选择器
          5 => string '' (length=0)伪类的值
          6 => string ' ' (length=1) 子结点标识
          7 => boolean false
 */
		return $selectors;//分组后的选择器
	}
	/**
	 * 获取所有的子结点
	 * 分析的主要入口函数
	 * 分析得到所有的结点集合
	 */
	private function initDom(){
		//遍历所有的数据一个字符一个字符分析
		
		$tmpStr=NULL;//临时缓存字符串
		for(;$this->pos<$this->size;$this->pos++){//遍历文件中的每一个文字，逐字分析
			

			$this->char = $this->doc[$this->pos];
			
			$oldCharInfo = $this->charInfo;//记录上一个字符的意义
			
			$this->charInfo = $this->getCharInfo();//读取当前字符的意义
			
			//log::debug("({$this->pos}){$this->char} = $this->charInfo");
			
			if($oldCharInfo === $this->charInfo){
				$tmpStr .= $this->char;
			}else{
				//将组成的信息保存好
				//发现一个新的结点
				if($oldCharInfo == TAG_NAME){//标签类型
					$tmpStr = strtolower(trim($tmpStr));
					end($this->tmpNodes);
					$lastKey = key($this->tmpNodes);//上一个结点的信息，父节点肯定没有封闭完成
					
					//自动封闭上一步没有封闭的一些自动封闭节点
					//$lastNode = $this->tmpNodes[$lastKey];

					//封闭上一个结点结束
					
					$newNode = new HtmlDomNode();
					$newNode->startPos = $this->pos - strlen($tmpStr) - 1;//减去标签占有的长度
					$newNode->tag = $tmpStr;//转小写后保存
					
					$newNode->parentNode = $lastKey;//指定结点的父结点
					$this->tmpNodes["N_".$this->nodeCount] = $newNode;//将新结点压入栈,返回新数组的长度
					
					if(!in_array($tmpStr, $this->ignoreNode))
					$len = array_push($this->tmpNodes[$lastKey]->children, "N_".$this->nodeCount);//将子结点压入栈
					//$newNode->brotherIndex = $len-1;
					
					$this->nodeCount++;
					//$this->tmpNodes[$this->nodePos]->children[]=$this->nodePos;
				}
				
				if($oldCharInfo === ATTR_NAME){//如果这是一个属性字符串
					$tmpAttrName = strtolower(trim($tmpStr));//属性
				}
				
				if($oldCharInfo === ATTR_VAL){//如果这是一个属性的值
					end($this->tmpNodes);
					$lastKey = key($this->tmpNodes);
					$this->tmpNodes[$lastKey]->attr[$tmpAttrName] = strtolower(trim($tmpStr));
				}
				
				if($oldCharInfo === TAG_END){//如果这是一个属性的值,有些单标签可能在这个地方结束，记录下来
					end($this->tmpNodes);
					$lastKey = key($this->tmpNodes);
					$this->tmpNodes[$lastKey]->endPos = $this->pos - 1;
					
					$_tag = $this->tmpNodes[$lastKey]->tag;
					
					
					if(in_array(strtolower($_tag),$this->singleLineNode,true)){//如果上一个结点是一个需要自动封闭的结点
						$this->nodes[$lastKey] = $this->tmpNodes[$lastKey];
						array_pop($this->tmpNodes);//将最后一个元素弹出
						end($this->tmpNodes);
						$lastKey = key($this->tmpNodes);
					}
					
					if(in_array(strtolower($_tag),$this->noChildNode,true)){
						//log::debug("_tag = $_tag({$this->pos})");
						//直接跳转到这个结束标签的附近；
						$pos = strpos($this->doc, "</$_tag>",$this->pos);
						if($pos !== false){
							$this->pos = $pos;
						}else{
							$this->pos = $this->size;
						}
						//log::debug("this->pos = {$this->pos}");
						continue;
					}
				}
				
				if($oldCharInfo === CLOSE_TAG_NAME){//是一个结束的标签名称
					$tmpStr = strtolower(trim($tmpStr));
					log::debug("tmpStr = $tmpStr");
					//当得到一个结束标签时
					//这个封闭，主要是记录这个标签的结束位置
					while(1){
						end($this->tmpNodes);
						$lastKey = key($this->tmpNodes);
						log::debug("lastNode = ".$this->tmpNodes[$lastKey]->tag);
						//最后一个元素
						if($this->tmpNodes[$lastKey]->tag==ROOT_NODE)break;//如果这个里面只有ROOT这个元素，照样要跳出
						
						if($tmpStr === $this->tmpNodes[$lastKey]->tag ){//如果找到的这个节点和堆栈中最后一个元素的值匹配
							$this->tmpNodes[$lastKey]->endPos = $this->pos;
							$this->nodes[$lastKey] = $this->tmpNodes[$lastKey];
							array_pop($this->tmpNodes);//将最后一个元素弹出
							break;//跳出循环
						}elseif(in_array($tmpStr,$this->singleLineNode,true)){//自动封闭结点，不进行匹配一切自动完成
							//$this->tmpNodes[$lastKey]->endPos = $this->pos;
							//$this->nodes[$lastKey] = $this->tmpNodes[$lastKey];
							//array_pop($this->tmpNodes);//将最后一个元素弹出
							break;//如果这是一些没有子结点的标签，则一样跳出
						}else{
							//标签不平衡的情况，也许是有单标签存在，也许就是找到的这个结束标签和目前数组中最后 一个不匹配
							//是不是应该放弃这个不平衡的结束标签？
							//$this->tmpNodes["N_$p"]->endPos = $this->tmpNodes["N_$len"]->startPos - 1;
							break;//暂时决定放弃这个标签
						}
					}
				}
				
				$tmpStr = $this->char;
			}
			 
			//将ROOT标签收尾
			if($this->pos == $this->size - 1){//游标到了最后，将根结点完结
				//log::debug("tmpNode:".var_export($this->tmpNodes,true));
				while(count($this->tmpNodes)>0){
					end($this->tmpNodes);
					$lastKey = key($this->tmpNodes);
					$this->nodes[$lastKey] = $this->tmpNodes[$lastKey];
					$this->nodes[$lastKey]->endPos = $this->size;
					array_pop($this->tmpNodes);
				}
				//$this->tmpNodes = NULL;//将临时数组置空，释放内存
			}
			//Log::debug("char = {$this->char}({$this->pos}) charInfo={$this->charInfo} status={$this->status} posLocation={$this->posLocation}");
		}
	}
	//获取当前字符的意义
	private function getCharInfo(){
		
		//正在读普通文本时，除非遇到左尖括号，否则永远认为它是一个普通文本
		if($this->status == READING_COMMON_TEXT){
			if($this->char==="<"){
				$this->status = READING_TAG_NAME;//直接进入读取标签名称的状态
				return TAG_START;
			}else{
				return COMMON_TEXT;
			}
		}
		
		//在读取名称的时候，除非遇到空格，否则永远认为是一个名称的一部分
		if($this->status == READING_TAG_NAME){
			if($this->char===SPACE){
				$this->status = READING_TAG_ATTR_NAME;//开始读取读值
				return NO_USE;
			}elseif($this->char===">"){//遇到一个结束符
				$this->status = READING_COMMON_TEXT;//读取常规文本
				return TAG_END;
			}elseif($this->char==="/"){//遇到一个反斜杠
				$this->status = READING_CLOSE_TAG;//读取常规文本
				return NO_USE;
			}else{
				return TAG_NAME;
			}
		}
		
		if($this->status == READING_TAG_ATTR_NAME){
			if($this->char==="="){//遇到等于号
				$this->status = READING_TAG_ATTR_VAL_START;//开始读取读值
				return EQUAL_SYMBOL;
			}elseif($this->char===">"){//遇到一个结束符
				$this->status = READING_COMMON_TEXT;//读取常规文本
				return TAG_END;
			}else{
				return ATTR_NAME;
			}
		}
		
		//开始读取属性值前面的引号
		if($this->status == READING_TAG_ATTR_VAL_START){
			if($this->char===SPACE){
				return NO_USE;
			}else{
				$this->status = READING_TAG_ATTR_VAL;
				if($this->char==="\"" || $this->char==="'"){
					$this->attrValSep = $this->char;//引号记录下来
					return VAL_LEFT;
				}else{
					return ATTR_VAL;
				}
			}
		}
		
		//开始读取属性值
		if($this->status == READING_TAG_ATTR_VAL){//正在读标签值的时候
			if($this->char===$this->attrValSep){
				//如果在读取标签的时候，遇到了引号（前面使用的那个字符），则属性读取完成
				$this->status = READING_TAG_ATTR_NAME;//继续进入找标签名称的状态
				$this->attrValSep = NULL;
				return VAL_RIGHT;
			}elseif($this->char===SPACE && $this->attrValSep==NULL){
				//如果在读取标签的时候，遇到了引号（前面使用的那个字符），则属性读取完成
				$this->status = READING_TAG_ATTR_NAME;//继续进入找标签名称的状态
				return NO_USE;
			}elseif($this->char===">" && $this->attrValSep==NULL){
				$this->status = READING_COMMON_TEXT;//读取常规文本
				return TAG_END;
			}else{
				//如果在读取标签属性值没有结束时，遇到任何信息都认为是值的一部分
				return ATTR_VAL;
			}
		}
		
		//读取结束标签状态
		if($this->status == READING_CLOSE_TAG){
			if($this->char===">"){
				$this->status = READING_COMMON_TEXT;//开始读取常规文本
				return TAG_OVER;
			}else{
				return CLOSE_TAG_NAME;
			}
		}

	}



	/**
	 * 析构函数
	 * 释放内存
	 */
	function __destruct() {
		$this->doc = null;
	}
	
	/**
	 * 转成字符串
	 */
	function __toString(){
		return $this->doc;
	}
	


}

/**
 * 
 * @author LiuYong
 * html的结点类
 */
class HtmlDomNode{
	
	public $tag = 'text';//标签类型，比如DIV,A,TEXT,FONT
	public $startPos;
	public $endPos;
	public $parentNode;
//	public $brotherIndex=0;//兄弟节点排行，从0开始，用于伪类的查询方便
//	public $nextNode;//下一个同辈结点
//	public $preNode;//前一个同辈结点
//	public $text_start;
//	public $text_end;
	public $attr=array();//属性
	public $children=array();//子结点们

	
	/**
	 * 构造函数
	 * @param HtmlDom $dom
	 */
	function __construct(){
	}
	
	/**
	 * 析构函数
	 */
	function __destruct(){
		
	}
	
	function __toString(){
		return "tag={$this->tag}<br>";
	}
	
}

?>